﻿using Daemon.Common.Cache;
using Daemon.Data.Substructure.Enums;
using Daemon.Repository.Contract;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Daemon.Repository.EF
{
	public class SessionItemRepository
	{
		private const string SessionsCacheKeyPrefix = "Sessions";

		private readonly IUserSessionRepository _userSessionRepository;

		private readonly IDaemonDistributedCache _daemonDistributedCache;

		private readonly IConfiguration _configuration;

		private readonly string _clientSessionsCacheKey;

		private readonly int _heartbeatInterval;

		private readonly int _ttlSeconds;

		public SessionItemRepository(IDaemonDistributedCache daemonDistributedCache, IConfiguration configuration, IUserSessionRepository userSessionRepository)
		{
			_daemonDistributedCache = daemonDistributedCache;
			_clientSessionsCacheKey = $"{SessionsCacheKeyPrefix}";
			_configuration = configuration;
			_heartbeatInterval = int.Parse(_configuration["heartBeatInterval"] ?? "60");
			_ttlSeconds = _heartbeatInterval + int.Parse(_configuration["heartBeatIntervalBuffer"] ?? "60");
			_userSessionRepository = userSessionRepository;
		}

		public bool CheckUserIsOnline(int userId)
		{
			Refresh();
			return GetOnlineUsers().Any(i => i.UserId == userId);
		}

		public void DeleteUserSession(int userId, LogoutReasonEnum logoutReason)
		{
			DeleteByUserId(userId, logoutReason);
			_userSessionRepository.SetLogoutTimeByUser(userId, logoutReason);
		}

		/// <summary>
		/// Delete session by user id.
		/// </summary>
		/// <param name="userid"></param>
		public void DeleteByUserId(int userid, LogoutReasonEnum logoutReason)
		{
			Refresh();
			var sessionItems = GetOnlineUsers().Where(i => i.UserId == userid);
			foreach (var sessionItem in sessionItems)
			{
				RemoveCache(sessionItem.Id, logoutReason);
			}
		}

		private IEnumerable<SessionItem> GetOnlineUsers()
		{
			// On exception an empty set of sessions is returned and dependent logic will see no online users
			try
			{
				var users = _daemonDistributedCache.HashGetAll<SessionItem>(_clientSessionsCacheKey).Select(r => r.Value);
				return users;
			}
			catch (Exception)
			{

			}

			return new List<SessionItem>();
		}

		private void RemoveCache(string sessionId, LogoutReasonEnum logoutReason)
		{
			// On exception user session will remain until it times out and is removed by successful refresh
			try
			{
				var sessionItem = _daemonDistributedCache.HashGet<SessionItem>(_clientSessionsCacheKey, sessionId);
				_userSessionRepository.UpdateLogoutTimeForSession(new List<int> { sessionItem.UserId }, logoutReason);
				_daemonDistributedCache.HashDelete<SessionItem>(_clientSessionsCacheKey, sessionId);
			}
			catch (Exception re)
			{
				Console.WriteLine(re.Message);
			}
		}

		private bool IsLive(SessionItem session)
		{
			return session.LiveTime.HasValue && session.LiveTime.Value.AddSeconds(_ttlSeconds) > DateTime.Now;
		}

		private void Refresh()
		{
			// On exception user sessions will momentarily be inaccurate until next successful refresh
			try
			{
				var clientSessions = _daemonDistributedCache.HashGetAll<SessionItem>(_clientSessionsCacheKey);
				var deadUserIds = clientSessions.GroupBy(s => s.Value.UserId).Where(g => g.All(s => !IsLive(s.Value))).Select(g => g.Key);
				var deadSessionFields = clientSessions.Where(i => !IsLive(i.Value)).Select(i => i.Key).ToList();
				_userSessionRepository.UpdateLogoutTimeForSession(deadUserIds.ToList(), LogoutReasonEnum.SessionTimeout);
				_daemonDistributedCache.HashDelete<SessionItem>(_clientSessionsCacheKey, deadSessionFields);
			}
			catch (Exception)
			{

			}
		}
	}

	/// <summary>
	/// Defines session item.
	/// </summary>
	public class SessionItem
	{
		/// <summary>
		/// Gets or sets client key.
		/// </summary>
		public string ClientKey { get; set; }

		/// <summary>
		/// Gets or sets id.
		/// </summary>
		public string Id { get; set; }

		/// <summary>
		/// Gets or sets user id.
		/// </summary>
		public int UserId { get; set; }

		/// <summary>
		/// Gets or sets DBID.
		/// </summary>
		public short? DBID { get; set; }

		/// <summary>
		/// Gets the interval time heart beat in seconds.
		/// </summary>
		public int HeartBeatInterval { get; set; }

		/// <summary>
		/// Gets or sets the live time.
		/// </summary>
		public DateTime? LiveTime { get; set; }

		/// <summary>
		/// Gets or sets the prefix
		/// </summary>
		public string Prefix { get; set; }
	}
}
